package top.dotgo.web.admin.oauth.api;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.endpoint.CheckTokenEndpoint;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
import org.springframework.security.oauth2.provider.token.ConsumerTokenServices;
import org.springframework.util.Assert;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.*;
import top.dotgo.kit.Const;
import top.dotgo.kit.ContextKit;
import top.dotgo.kit.ex.DotMsg;
import top.dotgo.web.admin.oauth.bean.TokenBean;
import top.dotgo.web.base.BaseController;
import top.dotgo.web.base.R;

import javax.validation.constraints.NotEmpty;
import java.security.Principal;
import java.util.Map;

/**
 * 登录token相关
 *
 * @author jornl
 * @date 2020/3/1 17:05 星期日
 */
@RestController
@Slf4j
@Validated
@RequestMapping("/oauth")
public class OauthController extends BaseController {

    public static final String REFRESH_TOKEN = "refresh_token";
    public static final String GRANT_TYPE = "grant_type";
    private final TokenEndpoint tokenEndpoint;
    private final CheckTokenEndpoint checkTokenEndpoint;
    private final ConsumerTokenServices consumerTokenServices;

    public OauthController(TokenEndpoint tokenEndpoint, CheckTokenEndpoint checkTokenEndpoint, ConsumerTokenServices consumerTokenServices) {
        this.tokenEndpoint = tokenEndpoint;
        this.checkTokenEndpoint = checkTokenEndpoint;
        this.consumerTokenServices = consumerTokenServices;
    }

    /**
     * 登录接口
     *
     * @param userName 用户名(或手机号)
     * @param password 密码
     * @param kaptcha  验证码
     * @return {@link TokenBean} TokenBean
     * @author jornl
     * @date 2020/3/3 23:33
     */
    @RequestMapping(value = "/token", method = {RequestMethod.POST, RequestMethod.GET})
    public R postAccessToken(Principal principal,
                             @RequestParam(value = "username", required = false) String userName,
                             @RequestParam(value = "password", required = false) String password,
                             @RequestParam(value = "refresh_token", required = false) String refreshToken,
                             @RequestParam(value = "kaptcha", required = false) String kaptcha) {
        if (principal == null) {
            return R.err(DotMsg.PARAM_ERR, "Authorization is error");
        }
        String grantType = "password";
        if (StrUtil.isEmpty(refreshToken)) {
            Assert.notNull(userName, "缺少请求参数 username");
            Assert.notNull(password, "缺少请求参数 password");
            return accessToken(principal, userName, password, kaptcha, grantType, null);
        } else {
            grantType = "refresh_token";
            return accessToken(principal, null, null, null, grantType, refreshToken);
        }
    }

    /**
     * 校验token是否合法
     *
     * @param token token
     * @return {@link R}
     * @author jornl
     * @date 2020/3/4 23:23
     */
    @PostMapping("/token/check")
    public R checkToken(@RequestParam("token") String token) {
        Map<String, ?> response = checkTokenEndpoint.checkToken(token);
        if (response != null && !response.isEmpty()) {
            return R.ok();
        }
        return R.err("token校验失败");
    }

    /**
     * 退出登录接口
     *
     * @param token token
     * @return R
     */
    @PostMapping("/logout")
    public R logout(@RequestHeader("token") @NotEmpty(message = "token不能为空") String token) {
        if (consumerTokenServices.revokeToken(token)) {
            ContextKit.logout();

            return R.ok();
        }
        return R.err("logout fail");
    }

    /**
     * 根据 OAuth2AccessToken 封装 R
     *
     * @param tokenBody tokenBody
     * @return R<TokenBean>
     */
    private R getResultToken(ResponseEntity<OAuth2AccessToken> tokenBody) {
        if (tokenBody == null || !tokenBody.hasBody() || tokenBody.getStatusCode() != HttpStatus.OK) {
            return R.err(DotMsg.PARAM_ERR, "登录失败");
        }
        DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) tokenBody.getBody();
        if (token == null) {
            return R.err(DotMsg.PARAM_ERR, "登录失败");
        }
        TokenBean bean = TokenBean.builder()
                .accessToken(token.getValue())
                .refreshToken(token.getRefreshToken().getValue())
                .expiration(token.getExpiration())
                .build();

        return R.ok(bean);
    }

    /**
     * 获取 token
     *
     * @param principal    principal
     * @param userName     userName
     * @param password     password
     * @param kaptcha      kaptcha
     * @param grantType    grantType
     * @param refreshToken refreshToken
     * @return R
     */
    private R accessToken(Principal principal, String userName, String password, String kaptcha, String grantType, String refreshToken) {
        //刷新token
        if (REFRESH_TOKEN.equals(grantType)) {
            return refreshToken(grantType, refreshToken, principal);
        }
        //小程序 不检测验证码
        if (ContextKit.getFrom().equals(Const.from.CM)) {
            password = userName;
        } else {
            R r = CaptchaController.checkKaptcha(kaptcha);
            if (!r.getCode().equals(DotMsg.SUCCESS_CODE)) {
                return r;
            }
        }

        Map<String, String> parameters = MapUtil
                .builder(GRANT_TYPE, "password")
                .put("username", userName)
                .put("password", password).build();
        ResponseEntity<OAuth2AccessToken> tokenBody;
        try {
            tokenBody = tokenEndpoint.postAccessToken(principal, parameters);
        } catch (HttpRequestMethodNotSupportedException e) {
            log.error(e.getMessage());
            return R.err("获取token失败,请稍候再试");
        }
        return getResultToken(tokenBody);
    }

    /**
     * 刷新token
     *
     * @param refreshToken refreshToken
     * @return {@link Boolean}
     */
    private R refreshToken(String grantType,
                           @NotEmpty(message = "refreshToken 不能为空") String refreshToken, Principal principal) {
        Map<String, String> parameters = MapUtil
                .builder(GRANT_TYPE, grantType)
                .put(REFRESH_TOKEN, refreshToken).build();
        ResponseEntity<OAuth2AccessToken> tokenBody;
        try {
            tokenBody = tokenEndpoint.postAccessToken(principal, parameters);
        } catch (HttpRequestMethodNotSupportedException e) {
            log.error(e.getMessage());
            return R.err("获取token失败,请稍候再试");
        }
        return getResultToken(tokenBody);
    }

}
